using System;
using System.Collections.Generic;
#if TEST
using Xunit;
#endif

namespace Fadd.Commands
{
	/// <summary>
	/// Class to generate readable names from arbitrary types
	/// </summary>
	public class ObjectProxyTypeNameGenerator
	{
		private readonly static Dictionary<Type, string> _typeNames = new Dictionary<Type, string>();
		private readonly static Dictionary<Type, string> _genericNames = new Dictionary<Type, string>();
		private readonly static Dictionary<Type, string> _typedNames = new Dictionary<Type, string>();
		private readonly static Dictionary<Type, string> _fullNames = new Dictionary<Type, string>();

		/// <summary>
		/// Removes any `X from generic type names, ie Dictionary`2.
		/// If the name lacks any `X entry ie Int32 it will simply be left untouched
		/// </summary>
		/// <param name="name">The name to strip from generic parameter count</param>
		/// <param name="arguments">Number of arguments held</param>
		private static string StripGenerics(string name, int arguments)
		{
			return name.Replace("`" + arguments, string.Empty);
		}

		/// <summary>
		/// Retrieves the name of the class, ie for <see cref="Dictionary{TKey,TValue}"/> => Dictionary
		/// </summary>
		/// <param name="type">The type to generate a name for</param>
		public string GetName(Type type)
		{
			Check.Require(type, "type");

			string name;
			if(!_typeNames.TryGetValue(type, out name))
			{
				name = StripGenerics(type.Name, type.GetGenericArguments().Length);
				_typeNames.Add(type, name);
			}

			return name;
		}

		/// <summary>
		/// <![CDATA[
		/// Retrieves name of class including any generic types, ie Dictionary<int, string>
		/// ]]>
		/// </summary>
		/// <param name="type">The type to generate a name for</param>
		public string GetGenericName(Type type)
		{
			Check.Require(type, "type");

			string name;
			if (!_genericNames.TryGetValue(type, out name))
			{
				if (type.IsGenericType)
				{
					name = StripGenerics(type.Name, type.GetGenericArguments().Length) + "<";
					foreach (Type argument in type.GetGenericArguments())
						name += GetGenericName(argument) + ", ";

					name = name.TrimEnd(',', ' ') + ">";
				}
				else
					name = type.Name;
				
				// Add the new converted name
				_genericNames.Add(type, name);
			}

			return name;
		}

		/// <summary>
		/// Retrieves a name of the type with any generic type arguments joined with name.
		/// </summary>
		/// <param name="type">The type to generate a name for</param>
		/// <example>
		/// <![CDATA[
		/// Dictionary<int, string> => DictionaryInt32String.
		/// Useful for naming ie arbitrary methods when building objects using reflection.
		/// ]]>
		/// </example>
		public string GetTypedName(Type type)
		{
			string name;
			if(!_typedNames.TryGetValue(type, out name))
			{
				name = GetName(type);
				foreach (Type argument in type.GetGenericArguments())
					name += GetTypedName(argument);

				_typedNames.Add(type, name);
			}

			return name;
		}

		/// <summary>
		/// Retrieves the full name of a class, ie System.Collections.Generic.Dictionary
		/// </summary>
		/// <param name="type">The type to return the full name for</param>
		public string GetFullName(Type type)
		{
			string name;
			if(!_fullNames.TryGetValue(type, out name))
			{
				name = type.Namespace + "." + GetName(type);
				_fullNames.Add(type, name);
			}

			return name;
		}

		#region Tests

#if TEST
		/// <summary>
		/// Test for generating names for generic types
		/// </summary>
		[Fact]
		private static void TestGetGeneric()
		{
			_fullNames.Clear();
			_genericNames.Clear();
			_typedNames.Clear();
			_typeNames.Clear();

			Type type = typeof (Dictionary<string, List<int>>);
			ObjectProxyTypeNameGenerator nameGenerator = new ObjectProxyTypeNameGenerator();
			Assert.Equal("Dictionary", nameGenerator.GetName(type));
			Assert.Equal("System.Collections.Generic.Dictionary", nameGenerator.GetFullName(type));
			Assert.Equal("DictionaryStringListInt32", nameGenerator.GetTypedName(type));
			Assert.Equal("Dictionary<String, List<Int32>>", nameGenerator.GetGenericName(type));
		}

		/// <summary>
		/// Test for generating names for generic types
		/// </summary>
		[Fact]
		private static void TestGetNormalType()
		{
			_fullNames.Clear();
			_genericNames.Clear();
			_typedNames.Clear();
			_typeNames.Clear();

			Type type = typeof(ObjectProxyTypeNameGenerator);
			ObjectProxyTypeNameGenerator nameGenerator = new ObjectProxyTypeNameGenerator();
			Assert.Equal("ObjectProxyTypeNameGenerator", nameGenerator.GetName(type));
			Assert.Equal("Fadd.Commands.ObjectProxyTypeNameGenerator", nameGenerator.GetFullName(type));
			Assert.Equal("ObjectProxyTypeNameGenerator", nameGenerator.GetTypedName(type));
			Assert.Equal("ObjectProxyTypeNameGenerator", nameGenerator.GetGenericName(type));

			// Try it again to make sure no duplicates are added
			Assert.Equal("ObjectProxyTypeNameGenerator", nameGenerator.GetGenericName(type));

			Assert.Equal(_fullNames.Count, 1);
			Assert.Equal(_typedNames.Count, 1);
			Assert.Equal(_typeNames.Count, 1);
		}
#endif
		#endregion
	}
}
